<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  
  <title>ThreadLocal解读 | hatena</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="ThreadLocal全面解析前置知识  具有一定的javase和javaweb基础 熟悉synchronized关键字 熟悉HashMap 熟悉 JDBC技术  学习目标  了解ThreadLocal的介绍  掌握ThreadLocal的运用场景 了解ThreadLocal的内部结构 了解ThreadLocal的核心方法源码 了解ThreadLocalMap的源码  1. ThreadLocal">
<meta property="og:type" content="article">
<meta property="og:title" content="ThreadLocal解读">
<meta property="og:url" content="https://gitee.com/lanceluot/blog.git/2024/02/23/ThreadLocal%E8%A7%A3%E8%AF%BB/index.html">
<meta property="og:site_name" content="hatena">
<meta property="og:description" content="ThreadLocal全面解析前置知识  具有一定的javase和javaweb基础 熟悉synchronized关键字 熟悉HashMap 熟悉 JDBC技术  学习目标  了解ThreadLocal的介绍  掌握ThreadLocal的运用场景 了解ThreadLocal的内部结构 了解ThreadLocal的核心方法源码 了解ThreadLocalMap的源码  1. ThreadLocal">
<meta property="og:locale" content="zn_CN">
<meta property="og:image" content="https://gitee.com/blog/img/008.png">
<meta property="og:image" content="https://gitee.com/blog/img/004.png">
<meta property="og:image" content="https://gitee.com/blog/img/005.png">
<meta property="og:image" content="https://gitee.com/blog/img/009.png">
<meta property="og:image" content="https://gitee.com/blog/img/006.png">
<meta property="article:published_time" content="2024-02-22T18:12:00.000Z">
<meta property="article:modified_time" content="2024-02-22T18:26:31.948Z">
<meta property="article:author" content="myself">
<meta property="article:tag" content="java">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/blog/img/008.png">
  
    <link rel="alternate" href="/blog/atom.xml" title="hatena" type="application/atom+xml">
  
  
    <link rel="shortcut icon" href="/blog/favicon.png">
  
  
    
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/typeface-source-code-pro@0.0.71/index.min.css">

  
  
<link rel="stylesheet" href="/blog/css/style.css">

  
    
<link rel="stylesheet" href="/blog/fancybox/jquery.fancybox.min.css">

  
  
<meta name="generator" content="Hexo 6.3.0"></head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/blog/" id="logo">hatena</a>
      </h1>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
        
          <a class="main-nav-link" href="/blog/">Home</a>
        
          <a class="main-nav-link" href="/blog/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
        
          <a class="nav-icon" href="/blog/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
        
        <a class="nav-icon nav-search-btn" title="Suche"><span class="fa fa-search"></span></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Suche"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="https://gitee.com/lanceluot/blog.git"></form>
      </div>
    </div>
  </div>
</header>

      <div class="outer">
        <section id="main"><article id="post-ThreadLocal解读" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/blog/2024/02/23/ThreadLocal%E8%A7%A3%E8%AF%BB/" class="article-date">
  <time class="dt-published" datetime="2024-02-22T18:12:00.000Z" itemprop="datePublished">2024-02-23</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="p-name article-title" itemprop="headline name">
      ThreadLocal解读
    </h1>
  

      </header>
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <h1 id="ThreadLocal全面解析"><a href="#ThreadLocal全面解析" class="headerlink" title="ThreadLocal全面解析"></a>ThreadLocal全面解析</h1><p><strong>前置知识</strong></p>
<ul>
<li>具有一定的javase和javaweb基础</li>
<li>熟悉synchronized关键字</li>
<li>熟悉HashMap</li>
<li>熟悉 JDBC技术</li>
</ul>
<p><strong>学习目标</strong></p>
<ul>
<li><strong>了解ThreadLocal的介绍</strong> </li>
<li><strong>掌握ThreadLocal的运用场景</strong></li>
<li><strong>了解ThreadLocal的内部结构</strong></li>
<li><strong>了解ThreadLocal的核心方法源码</strong></li>
<li><strong>了解ThreadLocalMap的源码</strong></li>
</ul>
<h2 id="1-ThreadLocal介绍"><a href="#1-ThreadLocal介绍" class="headerlink" title="1. ThreadLocal介绍"></a>1. ThreadLocal介绍</h2><span id="more"></span>

<h3 id="1-1-官方介绍"><a href="#1-1-官方介绍" class="headerlink" title="1.1 官方介绍"></a>1.1 官方介绍</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This class provides thread-local variables.  These variables differ from</span></span><br><span class="line"><span class="comment"> * their normal counterparts in that each thread that accesses one (via its</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> get&#125; or &#123;<span class="doctag">@code</span> set&#125; method) has its own, independently initialized</span></span><br><span class="line"><span class="comment"> * copy of the variable.  &#123;<span class="doctag">@code</span> ThreadLocal&#125; instances are typically private</span></span><br><span class="line"><span class="comment"> * static fields in classes that wish to associate state with a thread (e.g.,</span></span><br><span class="line"><span class="comment"> * a user ID or Transaction ID).</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;For example, the class below generates unique identifiers local to each</span></span><br><span class="line"><span class="comment"> * thread.</span></span><br><span class="line"><span class="comment"> * A thread&#x27;s id is assigned the first time it invokes &#123;<span class="doctag">@code</span> ThreadId.get()&#125;</span></span><br><span class="line"><span class="comment"> * and remains unchanged on subsequent calls.</span></span><br><span class="line"><span class="comment"> * &lt;pre&gt;</span></span><br><span class="line"><span class="comment"> * import java.util.concurrent.atomic.AtomicInteger;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * public class ThreadId &#123;</span></span><br><span class="line"><span class="comment"> *     // Atomic integer containing the next thread ID to be assigned</span></span><br><span class="line"><span class="comment"> *     private static final AtomicInteger nextId = new AtomicInteger(0);</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *     // Thread local variable containing each thread&#x27;s ID</span></span><br><span class="line"><span class="comment"> *     private static final ThreadLocal&amp;lt;Integer&amp;gt; threadId =</span></span><br><span class="line"><span class="comment"> *         new ThreadLocal&amp;lt;Integer&amp;gt;() &#123;</span></span><br><span class="line"><span class="comment"> *             &amp;#64;Override protected Integer initialValue() &#123;</span></span><br><span class="line"><span class="comment"> *                 return nextId.getAndIncrement();</span></span><br><span class="line"><span class="comment"> *         &#125;</span></span><br><span class="line"><span class="comment"> *     &#125;;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *     // Returns the current thread&#x27;s unique ID, assigning it if necessary</span></span><br><span class="line"><span class="comment"> *     public static int get() &#123;</span></span><br><span class="line"><span class="comment"> *         return threadId.get();</span></span><br><span class="line"><span class="comment"> *     &#125;</span></span><br><span class="line"><span class="comment"> * &#125;</span></span><br><span class="line"><span class="comment"> * &lt;/pre&gt;</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;Each thread holds an implicit reference to its copy of a thread-local</span></span><br><span class="line"><span class="comment"> * variable as long as the thread is alive and the &#123;<span class="doctag">@code</span> ThreadLocal&#125;</span></span><br><span class="line"><span class="comment"> * instance is accessible; after a thread goes away, all of its copies of</span></span><br><span class="line"><span class="comment"> * thread-local instances are subject to garbage collection (unless other</span></span><br><span class="line"><span class="comment"> * references to these copies exist).</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>  Josh Bloch and Doug Lea</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span>   1.2</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadLocal</span>&lt;T&gt; &#123;</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure>

<p>​	从Java官方文档中的描述：ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问（通过get和set方法访问）时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的，用于关联线程和线程上下文。</p>
<pre><code>   我们可以得知 ThreadLocal 的作用是：提供线程内的局部变量，不同的线程之间不会相互干扰，这种变量在线程的生命周期内起作用，减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
</code></pre>
   <figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">总结:</span><br><span class="line">1. 线程并发: 在多线程并发的场景下</span><br><span class="line">2. 传递数据: 我们可以通过ThreadLocal在同一线程，不同组件中传递公共变量</span><br><span class="line">3. 线程隔离: 每个线程的变量都是独立的，不会互相影响</span><br></pre></td></tr></table></figure>



<h3 id="1-2-基本使用"><a href="#1-2-基本使用" class="headerlink" title="1.2 基本使用"></a>1.2 基本使用</h3><h4 id="1-2-1-常用方法"><a href="#1-2-1-常用方法" class="headerlink" title="1.2.1 常用方法"></a>1.2.1 常用方法</h4><p>​	在使用之前,我们先来认识几个ThreadLocal的常用方法</p>
<table>
<thead>
<tr>
<th>方法声明</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>ThreadLocal()</td>
<td>创建ThreadLocal对象</td>
</tr>
<tr>
<td>public void set( T value)</td>
<td>设置当前线程绑定的局部变量</td>
</tr>
<tr>
<td>public T get()</td>
<td>获取当前线程绑定的局部变量</td>
</tr>
<tr>
<td>public void remove()</td>
<td>移除当前线程绑定的局部变量</td>
</tr>
</tbody></table>
<h3 id="1-3-ThreadLocal类与synchronized关键字"><a href="#1-3-ThreadLocal类与synchronized关键字" class="headerlink" title="1.3 ThreadLocal类与synchronized关键字"></a>1.3 ThreadLocal类与synchronized关键字</h3><h4 id="1-3-1-synchronized同步方式"><a href="#1-3-1-synchronized同步方式" class="headerlink" title="1.3.1 synchronized同步方式"></a>1.3.1 synchronized同步方式</h4><p>​	ThreadLocal强调的是线程数据隔离的问题，并不是多线程共享数据的问题.</p>
<h4 id="1-3-2-ThreadLocal与synchronized的区别"><a href="#1-3-2-ThreadLocal与synchronized的区别" class="headerlink" title="1.3.2 ThreadLocal与synchronized的区别"></a>1.3.2 ThreadLocal与synchronized的区别</h4><p>​	虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。</p>
<table>
<thead>
<tr>
<th></th>
<th>synchronized</th>
<th>ThreadLocal</th>
</tr>
</thead>
<tbody><tr>
<td>原理</td>
<td>同步机制采用’以时间换空间’的方式, 只提供了一份变量,让不同的线程排队访问</td>
<td>ThreadLocal采用’以空间换时间’的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰</td>
</tr>
<tr>
<td>侧重点</td>
<td>多个线程之间访问资源的同步</td>
<td>多线程中让每个线程之间的数据相互隔离</td>
</tr>
</tbody></table>
<figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">总结： 使用ThreadLocal和synchronized都能解决问题,但是使用ThreadLocal更为合适,因为这样可以使程序拥有更高的并发性。</span><br></pre></td></tr></table></figure>





<h4 id="2-3-2-ThreadLocal方案的好处"><a href="#2-3-2-ThreadLocal方案的好处" class="headerlink" title="2.3.2 ThreadLocal方案的好处"></a>2.3.2 ThreadLocal方案的好处</h4><p>从上述的案例中我们可以看到， 在一些特定场景下，ThreadLocal方案有两个突出的优势： </p>
<ol>
<li><p>传递数据 ： 保存每个线程绑定的数据，在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题</p>
</li>
<li><p>线程隔离 ： 各线程之间的数据相互隔离却又具备并发性，避免同步方式带来的性能损失</p>
</li>
</ol>
<h2 id="3-ThreadLocal的内部结构"><a href="#3-ThreadLocal的内部结构" class="headerlink" title="3. ThreadLocal的内部结构"></a>3. ThreadLocal的内部结构</h2><p>​	通过以上的学习，我们对ThreadLocal的作用有了一定的认识。现在我们一起来看一下ThreadLocal的内部结构，探究它能够实现线程数据隔离的原理。</p>
<h3 id="3-1-常见的误解"><a href="#3-1-常见的误解" class="headerlink" title="3.1  常见的误解"></a>3.1  常见的误解</h3><p>​	如果我们不去看源代码的话，可能会猜测<code>ThreadLocal</code>是这样子设计的：每个<code>ThreadLocal</code>都创建一个<code>Map</code>，然后用线程作为<code>Map</code>的<code>key</code>，要存储的局部变量作为<code>Map</code>的<code>value</code>，这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法，JDK最早期的<code>ThreadLocal</code> 确实是这样设计的，但现在早已不是了。</p>
<p><img src="/blog/img/008.png" alt="1582788729557"></p>
<h3 id="3-2-现在的设计"><a href="#3-2-现在的设计" class="headerlink" title="3.2  现在的设计"></a>3.2  现在的设计</h3><p>​	但是，JDK后面优化了设计方案，在JDK8中 <code>ThreadLocal</code>的设计是：每个<code>Thread</code>维护一个<code>ThreadLocalMap</code>，这个Map的<code>key</code>是<code>ThreadLocal</code>实例本身，<code>value</code>才是真正要存储的值<code>Object</code>。</p>
<p>具体的过程是这样的：</p>
<p>​	（1） 每个Thread线程内部都有一个Map (ThreadLocalMap)<br>​	（2） Map里面存储ThreadLocal对象（key）和线程的变量副本（value）<br>​	（3）Thread内部的Map是由ThreadLocal维护的，由ThreadLocal负责向map获取和设置线程的变量值。<br>​	（4）对于不同的线程，每次获取副本值时，别的线程并不能获取到当前线程的副本值，形成了副本的隔离，互不干扰。	</p>
<p><img src="/blog/img/004.png" alt="1574155226793"></p>
<h3 id="3-3-这样设计的好处"><a href="#3-3-这样设计的好处" class="headerlink" title="3.3 这样设计的好处"></a>3.3 这样设计的好处</h3><p>​	这个设计与我们一开始说的设计刚好相反，这样设计有如下两个优势：</p>
<p>（1） 这样设计之后每个<code>Map</code>存储的<code>Entry</code>数量就会变少。因为之前的存储数量由<code>Thread</code>的数量决定，现在是由<code>ThreadLocal</code>的数量决定。在实际运用当中，往往ThreadLocal的数量要少于Thread的数量。</p>
<p>（2） 当<code>Thread</code>销毁之后，对应的<code>ThreadLocalMap</code>也会随之销毁，能减少内存的使用。</p>
<h2 id="4-ThreadLocal的核心方法源码"><a href="#4-ThreadLocal的核心方法源码" class="headerlink" title="4. ThreadLocal的核心方法源码"></a>4. ThreadLocal的核心方法源码</h2><p>​	基于ThreadLocal的内部结构，我们继续分析它的核心方法源码，更深入的了解其操作原理。</p>
<p>除了构造方法之外， ThreadLocal对外暴露的方法有以下4个：</p>
<table>
<thead>
<tr>
<th>方法声明</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>protected T initialValue()</td>
<td>返回当前线程局部变量的初始值</td>
</tr>
<tr>
<td>public void set( T value)</td>
<td>设置当前线程绑定的局部变量</td>
</tr>
<tr>
<td>public T get()</td>
<td>获取当前线程绑定的局部变量</td>
</tr>
<tr>
<td>public void remove()</td>
<td>移除当前线程绑定的局部变量</td>
</tr>
</tbody></table>
<p>​	以下是这4个方法的详细源码分析(为了保证思路清晰, ThreadLocalMap部分暂时不展开,下一个知识点详解)</p>
<h3 id="4-1-set方法"><a href="#4-1-set方法" class="headerlink" title="4.1 set方法"></a>4.1 set方法</h3><p><strong>（1 ) 源码和对应的中文注释</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 设置当前线程对应的ThreadLocal的值</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> value 将要保存在当前线程对应的ThreadLocal的值</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123;</span><br><span class="line">       <span class="comment">// 获取当前线程对象</span></span><br><span class="line">       <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">       <span class="comment">// 获取此线程对象中维护的ThreadLocalMap对象</span></span><br><span class="line">       <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">       <span class="comment">// 判断map是否存在</span></span><br><span class="line">       <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">           <span class="comment">// 存在则调用map.set设置此实体entry</span></span><br><span class="line">           map.set(<span class="built_in">this</span>, value);</span><br><span class="line">       <span class="keyword">else</span></span><br><span class="line">           <span class="comment">// 1）当前线程Thread 不存在ThreadLocalMap对象</span></span><br><span class="line">           <span class="comment">// 2）则调用createMap进行ThreadLocalMap对象的初始化</span></span><br><span class="line">           <span class="comment">// 3）并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中</span></span><br><span class="line">           createMap(t, value);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 获取当前线程Thread对应维护的ThreadLocalMap </span></span><br><span class="line"><span class="comment">    * </span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span>  t the current thread 当前线程</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> the map 对应维护的ThreadLocalMap </span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   ThreadLocalMap <span class="title function_">getMap</span><span class="params">(Thread t)</span> &#123;</span><br><span class="line">       <span class="keyword">return</span> t.threadLocals;</span><br><span class="line">   &#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    *创建当前线程Thread对应维护的ThreadLocalMap </span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> t 当前线程</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> firstValue 存放到map中第一个entry的值</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">createMap</span><span class="params">(Thread t, T firstValue)</span> &#123;</span><br><span class="line">       <span class="comment">//这里的this是调用此方法的threadLocal</span></span><br><span class="line">       t.threadLocals = <span class="keyword">new</span> <span class="title class_">ThreadLocalMap</span>(<span class="built_in">this</span>, firstValue);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<p><strong>（2 )  代码执行流程</strong></p>
<p>​	A. 首先获取当前线程，并根据当前线程获取一个Map</p>
<p>​	B. 如果获取的Map不为空，则将参数设置到Map中（当前ThreadLocal的引用作为key）</p>
<p>​	C. 如果Map为空，则给该线程创建 Map，并设置初始值</p>
<h3 id="4-2-get方法"><a href="#4-2-get方法" class="headerlink" title="4.2 get方法"></a>4.2 get方法</h3><p><strong>（1 ) 源码和对应的中文注释</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 返回当前线程中保存ThreadLocal的值</span></span><br><span class="line"><span class="comment"> * 如果当前线程没有此ThreadLocal变量，</span></span><br><span class="line"><span class="comment"> * 则它会通过调用&#123;<span class="doctag">@link</span> #initialValue&#125; 方法进行初始化值</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 返回当前线程对应此ThreadLocal的值</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> T <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 获取当前线程对象</span></span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="comment">// 获取此线程对象中维护的ThreadLocalMap对象</span></span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="comment">// 如果此map存在</span></span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 以当前的ThreadLocal 为 key，调用getEntry获取对应的存储实体e</span></span><br><span class="line">        ThreadLocalMap.<span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> map.getEntry(<span class="built_in">this</span>);</span><br><span class="line">        <span class="comment">// 对e进行判空 </span></span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">            <span class="comment">// 获取存储实体 e 对应的 value值</span></span><br><span class="line">            <span class="comment">// 即为我们想要的当前线程对应此ThreadLocal的值</span></span><br><span class="line">            <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> (T)e.value;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    	初始化 : 有两种情况有执行当前代码</span></span><br><span class="line"><span class="comment">    	第一种情况: map不存在，表示此线程没有维护的ThreadLocalMap对象</span></span><br><span class="line"><span class="comment">    	第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">return</span> setInitialValue();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 初始化</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the initial value 初始化后的值</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> T <span class="title function_">setInitialValue</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 调用initialValue获取初始化的值</span></span><br><span class="line">    <span class="comment">// 此方法可以被子类重写, 如果不重写默认返回null</span></span><br><span class="line">    <span class="type">T</span> <span class="variable">value</span> <span class="operator">=</span> initialValue();</span><br><span class="line">    <span class="comment">// 获取当前线程对象</span></span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="comment">// 获取此线程对象中维护的ThreadLocalMap对象</span></span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="comment">// 判断map是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">        <span class="comment">// 存在则调用map.set设置此实体entry</span></span><br><span class="line">        map.set(<span class="built_in">this</span>, value);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment">// 1）当前线程Thread 不存在ThreadLocalMap对象</span></span><br><span class="line">        <span class="comment">// 2）则调用createMap进行ThreadLocalMap对象的初始化</span></span><br><span class="line">        <span class="comment">// 3）并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中</span></span><br><span class="line">        createMap(t, value);</span><br><span class="line">    <span class="comment">// 返回设置的值value</span></span><br><span class="line">    <span class="keyword">return</span> value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>（2 )  代码执行流程</strong> </p>
<p>​	A. 首先获取当前线程, 根据当前线程获取一个Map</p>
<p>​	B. 如果获取的Map不为空，则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e，否则转到D</p>
<p>​	C. 如果e不为null，则返回e.value，否则转到D</p>
<p>​	D. Map为空或者e为空，则通过initialValue函数获取初始值value，然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map</p>
<p>总结:  <strong>先获取当前线程的 ThreadLocalMap 变量，如果存在则返回值，不存在则创建并返回初始值。</strong></p>
<h3 id="4-3-remove方法"><a href="#4-3-remove方法" class="headerlink" title="4.3 remove方法"></a>4.3 remove方法</h3><p><strong>（1 ) 源码和对应的中文注释</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 删除当前线程中保存的ThreadLocal对应的实体entry</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">remove</span><span class="params">()</span> &#123;</span><br><span class="line">       <span class="comment">// 获取当前线程对象中维护的ThreadLocalMap对象</span></span><br><span class="line">        <span class="type">ThreadLocalMap</span> <span class="variable">m</span> <span class="operator">=</span> getMap(Thread.currentThread());</span><br><span class="line">       <span class="comment">// 如果此map存在</span></span><br><span class="line">        <span class="keyword">if</span> (m != <span class="literal">null</span>)</span><br><span class="line">           <span class="comment">// 存在则调用map.remove</span></span><br><span class="line">           <span class="comment">// 以当前ThreadLocal为key删除对应的实体entry</span></span><br><span class="line">            m.remove(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p><strong>（2 )  代码执行流程</strong></p>
<p>​	A. 首先获取当前线程，并根据当前线程获取一个Map</p>
<p>​	B. 如果获取的Map不为空，则移除当前ThreadLocal对象对应的entry</p>
<h3 id="4-4-initialValue方法"><a href="#4-4-initialValue方法" class="headerlink" title="4.4 initialValue方法"></a><strong>4.4 initialValue方法</strong></h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">  * 返回当前线程对应的ThreadLocal的初始值</span></span><br><span class="line"><span class="comment">  </span></span><br><span class="line"><span class="comment">  * 此方法的第一次调用发生在，当线程通过get方法访问此线程的ThreadLocal值时</span></span><br><span class="line"><span class="comment">  * 除非线程先调用了set方法，在这种情况下，initialValue 才不会被这个线程调用。</span></span><br><span class="line"><span class="comment">  * 通常情况下，每个线程最多调用一次这个方法。</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * &lt;p&gt;这个方法仅仅简单的返回null &#123;<span class="doctag">@code</span> null&#125;;</span></span><br><span class="line"><span class="comment">  * 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值，</span></span><br><span class="line"><span class="comment">  * 必须通过子类继承&#123;<span class="doctag">@code</span> ThreadLocal&#125; 的方式去重写此方法</span></span><br><span class="line"><span class="comment">  * 通常, 可以通过匿名内部类的方式实现</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * <span class="doctag">@return</span> 当前ThreadLocal的初始值</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="keyword">protected</span> T <span class="title function_">initialValue</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>​	此方法的作用是 返回该线程局部变量的初始值。</p>
<p>（1） 这个方法是一个延迟调用方法，从上面的代码我们得知，在set方法还未调用而先调用了get方法时才执行，并且仅执行1次。</p>
<p>（2）这个方法缺省实现直接返回一个<code>null</code>。</p>
<p>（3）如果想要一个除null之外的初始值，可以重写此方法。（备注： 该方法是一个<code>protected</code>的方法，显然是为了让子类覆盖而设计的）</p>
<h2 id="5-ThreadLocalMap源码分析"><a href="#5-ThreadLocalMap源码分析" class="headerlink" title="5. ThreadLocalMap源码分析"></a>5. ThreadLocalMap源码分析</h2><p>​	在分析ThreadLocal方法的时候，我们了解到ThreadLocal的操作实际上是围绕ThreadLocalMap展开的。ThreadLocalMap的源码相对比较复杂, 我们从以下三个方面进行讨论。</p>
<h3 id="5-1-基本结构"><a href="#5-1-基本结构" class="headerlink" title="5.1 基本结构"></a>5.1 基本结构</h3><p>​	ThreadLocalMap是ThreadLocal的内部类，没有实现Map接口，用独立的方式实现了Map的功能，其内部的Entry也是独立实现。</p>
<p>   <img src="/blog/img/005.png" alt="1574266262577"></p>
<p><strong>（1） 成员变量</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 初始容量 —— 必须是2的整次幂</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">INITIAL_CAPACITY</span> <span class="operator">=</span> <span class="number">16</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 存放数据的table，Entry类的定义在下面分析</span></span><br><span class="line"><span class="comment"> * 同样，数组长度必须是2的整次幂。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> Entry[] table;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 数组里面entrys的个数，可以用于判断table当前使用量是否超过阈值。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 进行扩容的阈值，表使用量大于它的时候进行扩容。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> threshold; <span class="comment">// Default to 0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>​	跟HashMap类似，INITIAL_CAPACITY代表这个Map的初始容量；table 是一个Entry 类型的数组，用于存储数据；size 代表表中的存储数目； threshold 代表需要扩容时对应 size 的阈值。</p>
<p>链接：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/acfd2239c9f4">https://www.jianshu.com/p/acfd2239c9f4</a></p>
<p>来源：简书</p>
<p>著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</p>
<p><strong>（2） 存储结构 - Entry</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Entry继承WeakReference，并且用ThreadLocal作为key.</span></span><br><span class="line"><span class="comment"> * 如果key为null(entry.get() == null)，意味着key不再被引用，</span></span><br><span class="line"><span class="comment"> * 因此这时候entry也可以从table中清除。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span> <span class="keyword">extends</span> <span class="title class_">WeakReference</span>&lt;ThreadLocal&lt;?&gt;&gt; &#123;</span><br><span class="line">    <span class="comment">/** The value associated with this ThreadLocal. */</span></span><br><span class="line">    Object value;</span><br><span class="line"></span><br><span class="line">    Entry(ThreadLocal&lt;?&gt; k, Object v) &#123;</span><br><span class="line">        <span class="built_in">super</span>(k);</span><br><span class="line">        value = v;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>​	 在ThreadLocalMap中，也是用Entry来保存K-V结构数据的。不过Entry中的key只能是ThreadLocal对象，这点在构造方法中已经限定死了。</p>
<p>​	另外，Entry继承WeakReference，也就是key（ThreadLocal）是弱引用，其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。</p>
<h3 id="5-2-弱引用和内存泄漏"><a href="#5-2-弱引用和内存泄漏" class="headerlink" title="5.2 弱引用和内存泄漏"></a>5.2 弱引用和内存泄漏</h3><p>​	有些程序员在使用ThreadLocal的过程中会发现有内存泄漏的情况发生，就猜测这个内存泄漏跟Entry中使用了弱引用的key有关系。这个理解其实是不对的。</p>
<p>​	我们先来回顾这个问题中涉及的几个名词概念，再来分析问题。</p>
<p>   <strong>（1） 内存泄漏相关概念</strong></p>
<ul>
<li>Memory overflow:内存溢出，没有足够的内存提供申请者使用。</li>
<li>Memory leak: 内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放，造成系统内存的浪费，导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。</li>
</ul>
<p>	</p>
<p><strong>（2）  弱引用相关概念</strong></p>
<p>​	Java中的引用有4种类型： 强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用：</p>
<p>​	<strong>强引用（“Strong” Reference）</strong>，就是我们最常见的普通对象引用，只要还有强引用指向一个对象，就能表明对象还“活着”，垃圾回收器就不会回收这种对象。</p>
<p>​	<strong>弱引用（WeakReference）</strong>，垃圾回收器一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。</p>
<p><strong>（3） 如果key使用强引用</strong></p>
<p>​	假设ThreadLocalMap中的key使用了强引用，那么会出现内存泄漏吗？</p>
<p>​	此时ThreadLocal的内存图（实线表示强引用）如下： </p>
<p><img src="/blog/img/009.png" alt="1582902708234"></p>
<p>​	假设在业务代码中使用完ThreadLocal ，threadLocal Ref被回收了。</p>
<p>​	但是因为threadLocalMap的Entry强引用了threadLocal，造成threadLocal无法被回收。</p>
<p>​	在没有手动删除这个Entry以及CurrentThread依然运行的前提下，始终有强引用链 threadRef-&gt;currentThread-&gt;threadLocalMap-&gt;entry，Entry就不会被回收（Entry中包括了ThreadLocal实例和value），导致Entry内存泄漏。</p>
<p>​	也就是说，ThreadLocalMap中的key使用了强引用， 是无法完全避免内存泄漏的。</p>
<p><strong>（5）如果key使用弱引用</strong></p>
<p>​	那么ThreadLocalMap中的key使用了弱引用，会出现内存泄漏吗？</p>
<p>​	此时ThreadLocal的内存图（实线表示强引用，虚线表示弱引用）如下： </p>
<p><img src="/blog/img/006.png" alt="1582907143471"></p>
<p>​	</p>
<p>​	同样假设在业务代码中使用完ThreadLocal ，threadLocal Ref被回收了。</p>
<p>​	由于ThreadLocalMap只持有ThreadLocal的弱引用，没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收，此时Entry中的key&#x3D;null。</p>
<p>​	但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下，也存在有强引用链 threadRef-&gt;currentThread-&gt;threadLocalMap-&gt;entry -&gt; value ，value不会被回收， 而这块value永远不会被访问到了，导致value内存泄漏。</p>
<p>​	也就是说，ThreadLocalMap中的key使用了弱引用， 也有可能内存泄漏。</p>
<p><strong>（6）出现内存泄漏的真实原因</strong></p>
<p>​	比较以上两种情况，我们就会发现，内存泄漏的发生跟ThreadLocalMap中的key是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢？</p>
<p>​	细心的同学会发现，在以上两种内存泄漏的情况中，都有两个前提：</p>
<pre><code> 1. 没有手动删除这个Entry
 2. CurrentThread依然运行
</code></pre>
<p>​        第一点很好理解，只要在使用完ThreadLocal，调用其remove方法删除对应的Entry，就能避免内存泄漏。</p>
<p>​	第二点稍微复杂一点， 由于ThreadLocalMap是Thread的一个属性，被当前线程所引用，所以它的生命周期跟Thread一样长。那么在使用完ThreadLocal的使用，如果当前Thread也随之执行结束，ThreadLocalMap自然也会被gc回收，从根源上避免了内存泄漏。</p>
<p>​	综上，<strong>ThreadLocal内存泄漏的根源是</strong>：由于ThreadLocalMap的生命周期跟Thread一样长，如果没有手动删除对应key就会导致内存泄漏。</p>
<p><strong>（7） 为什么使用弱引用</strong></p>
<p>​	根据刚才的分析, 我们知道了： 无论ThreadLocalMap中的key使用哪种类型引用都无法完全避免内存泄漏，跟使用弱引用没有关系。</p>
<p>​	要避免内存泄漏有两种方式：</p>
<ol>
<li><p>使用完ThreadLocal，调用其remove方法删除对应的Entry</p>
</li>
<li><p>使用完ThreadLocal，当前Thread也随之运行结束</p>
</li>
</ol>
<pre><code>相对第一种方式，第二种方式显然更不好控制，特别是使用线程池的时候，线程结束是不会销毁的。
</code></pre>
<p>​	也就是说，只要记得在使用完ThreadLocal及时的调用remove，无论key是强引用还是弱引用都不会有问题。那么为什么key要用弱引用呢？</p>
<p>​	事实上，在ThreadLocalMap中的set&#x2F;getEntry方法中，会对key为null（也即是ThreadLocal为null）进行判断，如果为null的话，那么是会对value置为null的。</p>
<p>​	这就意味着使用完ThreadLocal，CurrentThread依然运行的前提下，就算忘记调用remove方法，<strong>弱引用比强引用可以多一层保障</strong>：弱引用的ThreadLocal会被回收，对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除，从而避免内存泄漏。</p>
<h3 id="5-3-hash冲突的解决"><a href="#5-3-hash冲突的解决" class="headerlink" title="5.3 hash冲突的解决"></a>5.3 hash冲突的解决</h3><p>​	hash冲突的解决是Map中的一个重要内容。我们以hash冲突的解决为线索，来研究一下ThreadLocalMap的核心源码。</p>
<p><strong>（1） 首先从ThreadLocal的set() 方法入手</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123;</span><br><span class="line">      <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">      ThreadLocal.<span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">      <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">          <span class="comment">//调用了ThreadLocalMap的set方法</span></span><br><span class="line">          map.set(<span class="built_in">this</span>, value);</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">          createMap(t, value);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  ThreadLocal.ThreadLocalMap <span class="title function_">getMap</span><span class="params">(Thread t)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> t.threadLocals;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">createMap</span><span class="params">(Thread t, T firstValue)</span> &#123;</span><br><span class="line">      	<span class="comment">//调用了ThreadLocalMap的构造方法</span></span><br><span class="line">      t.threadLocals = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>.ThreadLocalMap(<span class="built_in">this</span>, firstValue);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>这个方法我们刚才分析过, 其作用是设置当前线程绑定的局部变量 : </p>
<p>​	A. 首先获取当前线程，并根据当前线程获取一个Map</p>
<p>​	B. 如果获取的Map不为空，则将参数设置到Map中（当前ThreadLocal的引用作为key）</p>
<p>​		<strong>(这里调用了ThreadLocalMap的set方法)</strong></p>
<p>​	C. 如果Map为空，则给该线程创建 Map，并设置初始值</p>
<p>​		<strong>(这里调用了ThreadLocalMap的构造方法)</strong></p>
<p>这段代码有两个地方分别涉及到ThreadLocalMap的两个方法, 我们接着分析这两个方法。</p>
<p>**（2）构造方法<code>ThreadLocalMap(ThreadLocal&lt;?&gt; firstKey, Object firstValue)**</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment">  * firstKey : 本ThreadLocal实例(this)</span></span><br><span class="line"><span class="comment">  * firstValue ： 要保存的线程本地变量</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">ThreadLocalMap(ThreadLocal&lt;?&gt; firstKey, Object firstValue) &#123;</span><br><span class="line">        <span class="comment">//初始化table</span></span><br><span class="line">        table = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>.ThreadLocalMap.Entry[INITIAL_CAPACITY];</span><br><span class="line">        <span class="comment">//计算索引(重点代码）</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> firstKey.threadLocalHashCode &amp; (INITIAL_CAPACITY - <span class="number">1</span>);</span><br><span class="line">        <span class="comment">//设置值</span></span><br><span class="line">        table[i] = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>.ThreadLocalMap.Entry(firstKey, firstValue);</span><br><span class="line">        size = <span class="number">1</span>;</span><br><span class="line">        <span class="comment">//设置阈值</span></span><br><span class="line">        setThreshold(INITIAL_CAPACITY);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>​	构造函数首先创建一个长度为16的Entry数组，然后计算出firstKey对应的索引，然后存储到table中，并设置size和threshold。</p>
<p>​	<strong>重点分析</strong>： <code>int i = firstKey.threadLocalHashCode &amp; (INITIAL_CAPACITY - 1)</code>。</p>
<p>a. 关于<code>firstKey.threadLocalHashCode</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> 	<span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">threadLocalHashCode</span> <span class="operator">=</span> nextHashCode();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">nextHashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> nextHashCode.getAndAdd(HASH_INCREMENT);</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//AtomicInteger是一个提供原子操作的Integer类，通过线程安全的方式操作加减,适合高并发情况下的使用</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">AtomicInteger</span> <span class="variable">nextHashCode</span> <span class="operator">=</span>  <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">     <span class="comment">//特殊的hash值</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">HASH_INCREMENT</span> <span class="operator">=</span> <span class="number">0x61c88647</span>;</span><br></pre></td></tr></table></figure>

<p>​	这里定义了一个AtomicInteger类型，每次获取当前值并加上HASH_INCREMENT，<code>HASH_INCREMENT = 0x61c88647</code>,这个值跟斐波那契数列（黄金分割数）有关，其主要目的就是为了让哈希码能均匀的分布在2的n次方的数组里, 也就是Entry[] table中，这样做可以尽量避免hash冲突。</p>
<p>b. 关于<code>&amp; (INITIAL_CAPACITY - 1)</code></p>
<p>​	计算hash的时候里面采用了hashCode &amp; (size - 1)的算法，这相当于取模运算hashCode % size的一个更高效的实现。正是因为这种算法，我们要求size必须是2的整次幂，这也能保证在索引不越界的前提下，使得hash发生冲突的次数减小。</p>
<p><strong>（3） ThreadLocalMap中的set方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(ThreadLocal&lt;?&gt; key, Object value)</span> &#123;</span><br><span class="line">        ThreadLocal.ThreadLocalMap.Entry[] tab = table;</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> tab.length;</span><br><span class="line">        <span class="comment">//计算索引(重点代码，刚才分析过了）</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> key.threadLocalHashCode &amp; (len-<span class="number">1</span>);</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 使用线性探测法查找元素（重点代码）</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">for</span> (ThreadLocal.ThreadLocalMap.<span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> tab[i];</span><br><span class="line">             e != <span class="literal">null</span>;</span><br><span class="line">             e = tab[i = nextIndex(i, len)]) &#123;</span><br><span class="line">            ThreadLocal&lt;?&gt; k = e.get();</span><br><span class="line">            <span class="comment">//ThreadLocal 对应的 key 存在，直接覆盖之前的值</span></span><br><span class="line">            <span class="keyword">if</span> (k == key) &#123;</span><br><span class="line">                e.value = value;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// key为 null，但是值不为 null，说明之前的 ThreadLocal 对象已经被回收了，</span></span><br><span class="line">           <span class="comment">// 当前数组中的 Entry 是一个陈旧（stale）的元素</span></span><br><span class="line">            <span class="keyword">if</span> (k == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">//用新元素替换陈旧的元素，这个方法进行了不少的垃圾清理动作，防止内存泄漏</span></span><br><span class="line">                replaceStaleEntry(key, value, i);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    </span><br><span class="line">    	<span class="comment">//ThreadLocal对应的key不存在并且没有找到陈旧的元素，则在空元素的位置创建一个新的Entry。</span></span><br><span class="line">            tab[i] = <span class="keyword">new</span> <span class="title class_">Entry</span>(key, value);</span><br><span class="line">            <span class="type">int</span> <span class="variable">sz</span> <span class="operator">=</span> ++size;</span><br><span class="line">            <span class="comment">/**</span></span><br><span class="line"><span class="comment">             * cleanSomeSlots用于清除那些e.get()==null的元素，</span></span><br><span class="line"><span class="comment">             * 这种数据key关联的对象已经被回收，所以这个Entry(table[index])可以被置null。</span></span><br><span class="line"><span class="comment">             * 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3)，那么进行				 * rehash（执行一次全表的扫描清理工作）</span></span><br><span class="line"><span class="comment">             */</span></span><br><span class="line">            <span class="keyword">if</span> (!cleanSomeSlots(i, sz) &amp;&amp; sz &gt;= threshold)</span><br><span class="line">                rehash();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取环形数组的下一个索引</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">nextIndex</span><span class="params">(<span class="type">int</span> i, <span class="type">int</span> len)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ((i + <span class="number">1</span> &lt; len) ? i + <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>​	代码执行流程：</p>
<p>A. 首先还是根据key计算出索引 i，然后查找i位置上的Entry，</p>
<p>B. 若是Entry已经存在并且key等于传入的key，那么这时候直接给这个Entry赋新的value值,</p>
<p>C. 若是Entry存在，但是key为null，则调用replaceStaleEntry来更换这个key为空的Entry,</p>
<p>D. 不断循环检测，直到遇到为null的地方，这时候要是还没在循环过程中return，那么就在这个null的位置新建一个Entry，并且插入，同时size增加1。</p>
<p>​    最后调用cleanSomeSlots，清理key为null的Entry，最后返回是否清理了Entry，接下来再判断sz 是否&gt;&#x3D; thresgold达到了rehash的条件，达到的话就会调用rehash函数执行一次全表的扫描清理。</p>
<p><strong>重点分析</strong> ： ThreadLocalMap使用<code>线性探测法</code>来解决哈希冲突的。</p>
<p>​	该方法一次探测下一个地址，直到有空的地址后插入，若整个空间都找不到空余的地址，则产生溢出。</p>
<p>​	举个例子，假设当前table长度为16，也就是说如果计算出来key的hash值为14，如果table[14]上已经有值，并且其key与当前key不一致，那么就发生了hash冲突，这个时候将14加1得到15，取table[15]进行判断，这个时候如果还是冲突会回到0，取table[0],以此类推，直到可以插入。</p>
<p>​	按照上面的描述，可以把Entry[]  table看成一个环形数组。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://gitee.com/lanceluot/blog.git/2024/02/23/ThreadLocal%E8%A7%A3%E8%AF%BB/" data-id="clsxjlpso0000i45bhz3gd9z0" data-title="ThreadLocal解读" class="article-share-link"><span class="fa fa-share">Teilen</span></a>
      
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog/tags/java/" rel="tag">java</a></li></ul>

    </footer>
  </div>
  
    
<nav id="article-nav">
  
    <a href="/blog/2024/02/23/redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Neuer</strong>
      <div class="article-nav-title">
        
          redis学习笔记
        
      </div>
    </a>
  
  
    <a href="/blog/2024/01/14/%E5%A4%96%E5%8D%96%E9%A1%B9%E7%9B%AE%E5%86%8D%E5%89%96%E6%9E%90/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Älter</strong>
      <div class="article-nav-title">外卖项目再剖析</div>
    </a>
  
</nav>

  
</article>


</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Kategorien</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA/">计算机</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tags</h3>
    <div class="widget">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/BUG/" rel="tag">BUG</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/RPC/" rel="tag">RPC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/java/" rel="tag">java</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/java%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/" rel="tag">java后端开发</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/java%E6%BA%90%E7%A0%81/" rel="tag">java源码</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jvm/" rel="tag">jvm</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/leetcode/" rel="tag">leetcode</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/leetcode%E9%A2%98%E8%A7%A3/" rel="tag">leetcode题解</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/rabbitmq/" rel="tag">rabbitmq</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/shell/" rel="tag">shell</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/spring/" rel="tag">spring</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/springboot/" rel="tag">springboot</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/test/" rel="tag">test</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E4%BD%9C%E4%B8%9A/" rel="tag">作业</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%AE%9E%E9%AA%8C/" rel="tag">实验</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%AE%9E%E9%AA%8C3/" rel="tag">实验3</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%84%9F%E6%83%B3/" rel="tag">感想</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" rel="tag">操作系统</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/" rel="tag">数据结构与算法</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%B5%8B%E8%AF%95/" rel="tag">测试</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%96%91%E9%97%AE/" rel="tag">疑问</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%AC%94%E8%AE%B0/" rel="tag">笔记</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%AE%97%E6%B3%95/" rel="tag">算法</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" rel="tag">设计模式</a></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E9%97%AE%E9%A2%98/" rel="tag">问题</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tag Cloud</h3>
    <div class="widget tagcloud">
      <a href="/blog/tags/BUG/" style="font-size: 10px;">BUG</a> <a href="/blog/tags/RPC/" style="font-size: 10px;">RPC</a> <a href="/blog/tags/git/" style="font-size: 10px;">git</a> <a href="/blog/tags/java/" style="font-size: 20px;">java</a> <a href="/blog/tags/java%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/" style="font-size: 10px;">java后端开发</a> <a href="/blog/tags/java%E6%BA%90%E7%A0%81/" style="font-size: 10px;">java源码</a> <a href="/blog/tags/jvm/" style="font-size: 10px;">jvm</a> <a href="/blog/tags/leetcode/" style="font-size: 10px;">leetcode</a> <a href="/blog/tags/leetcode%E9%A2%98%E8%A7%A3/" style="font-size: 15px;">leetcode题解</a> <a href="/blog/tags/rabbitmq/" style="font-size: 10px;">rabbitmq</a> <a href="/blog/tags/redis/" style="font-size: 10px;">redis</a> <a href="/blog/tags/shell/" style="font-size: 10px;">shell</a> <a href="/blog/tags/spring/" style="font-size: 10px;">spring</a> <a href="/blog/tags/springboot/" style="font-size: 10px;">springboot</a> <a href="/blog/tags/test/" style="font-size: 10px;">test</a> <a href="/blog/tags/%E4%BD%9C%E4%B8%9A/" style="font-size: 10px;">作业</a> <a href="/blog/tags/%E5%AE%9E%E9%AA%8C/" style="font-size: 15px;">实验</a> <a href="/blog/tags/%E5%AE%9E%E9%AA%8C3/" style="font-size: 10px;">实验3</a> <a href="/blog/tags/%E6%84%9F%E6%83%B3/" style="font-size: 10px;">感想</a> <a href="/blog/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" style="font-size: 10px;">操作系统</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/" style="font-size: 10px;">数据结构与算法</a> <a href="/blog/tags/%E6%B5%8B%E8%AF%95/" style="font-size: 10px;">测试</a> <a href="/blog/tags/%E7%96%91%E9%97%AE/" style="font-size: 10px;">疑问</a> <a href="/blog/tags/%E7%AC%94%E8%AE%B0/" style="font-size: 10px;">笔记</a> <a href="/blog/tags/%E7%AE%97%E6%B3%95/" style="font-size: 10px;">算法</a> <a href="/blog/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size: 15px;">设计模式</a> <a href="/blog/tags/%E9%97%AE%E9%A2%98/" style="font-size: 10px;">问题</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Archiv</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2024/05/">May 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2024/04/">April 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2024/03/">March 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2024/02/">February 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2024/01/">January 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2023/10/">October 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2023/09/">September 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2023/08/">August 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2023/05/">May 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2023/04/">April 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2022/12/">December 2022</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">letzter Beitrag</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/blog/2024/05/01/%E7%AE%97%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90%E5%AE%9E%E9%AA%8C%E4%B8%89-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/">算法设计与分析实验三-动态规划</a>
          </li>
        
          <li>
            <a href="/blog/2024/05/01/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%AE%9E%E9%AA%8C3-%E6%96%87%E4%BB%B6io%E7%BC%96%E7%A8%8B/">嵌入式实验3-文件io编程</a>
          </li>
        
          <li>
            <a href="/blog/2024/04/30/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AE%9E%E8%B7%B5-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAfat12%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/">操作系统实践-实现一个fat12文件系统</a>
          </li>
        
          <li>
            <a href="/blog/2024/04/25/LambdaQuery-%E5%92%8C-Query%E7%9A%84%E5%8C%BA%E5%88%AB/">LambdaQuery 和 Query的区别</a>
          </li>
        
          <li>
            <a href="/blog/2024/04/25/LambdaQuery%E5%92%8CQuery%E7%9A%84%E5%8C%BA%E5%88%AB/">LambdaQuery和Query的区别</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      
      &copy; 2024 myself<br>
      Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>

    </div>
    <nav id="mobile-nav">
  
    <a href="/blog/" class="mobile-nav-link">Home</a>
  
    <a href="/blog/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    


<script src="/blog/js/jquery-3.6.4.min.js"></script>



  
<script src="/blog/fancybox/jquery.fancybox.min.js"></script>




<script src="/blog/js/script.js"></script>





  </div>
</body>
</html>